#include "application.hpp"

#include <limits.h> // PATH_MAX

#include <atomic>
#include <thread>
#include <cstring>

#if defined(__linux__) || defined(__linux)
#  define APPLICATION_OS_LINUX
#elif defined(__APPLE__) || defined(MACOSX) || defined(macintosh) || defined(Macintosh)
#  define APPLICATION_OS_MACOS
#elif !defined(SAG_COM) && (!defined(WINAPI_FAMILY) || WINAPI_FAMILY==WINAPI_FAMILY_DESKTOP_APP) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__))
#  define APPLICATION_OS_WIN32
#  define APPLICATION_OS_WIN64
#elif !defined(SAG_COM) && (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
#  define APPLICATION_OS_WIN32
#elif defined(__QNXNTO__) || defined(__QNX__)
#  define APPLICATION_OS_QNX
#else
#  error "Unsupported platform"
#endif

#if defined(APPLICATION_OS_WIN32) || defined(APPLICATION_OS_WIN64)
#  define APPLICATION_OS_WINDOWS
#endif

#if defined(APPLICATION_OS_LINUX)
#  include <unistd.h> // readlink(), getpid()
#  ifndef PATH_MAX
#    define PATH_MAX 4096
#  endif
#  define APPLICATION_MAX_PATH  PATH_MAX
#elif defined(APPLICATION_OS_WINDOWS)
#  include <Windows.h>
#  define APPLICATION_MAX_PATH  MAX_PATH
#elif defined(APPLICATION_OS_MACOS)
#  include <unistd.h> // getpid()
#  include <libproc.h>
#  define APPLICATION_MAX_PATH  PROC_PIDPATHINFO_MAXSIZE
#elif defined(APPLICATION_OS_QNX)
#  include <fstream>
#  include <unistd.h> // getpid()
#  include <stdlib.h>
#  ifndef PATH_MAX
#    define PATH_MAX 4096
#  endif
#  define APPLICATION_MAX_PATH  PATH_MAX
#else // try to include unistd.h on other platform
#  include <unistd.h>
#endif

#ifndef APPLICATION_MAX_PATH
#  define APPLICATION_MAX_PATH  4096
#endif

namespace {

static std::string GetDirNameFromPath(const std::string& path)
{
    std::size_t pos = path.find_last_of("/\\");
    if (pos == std::string::npos) {
        return std::string();
    }
    return path.substr(0, pos);
}

static std::string GetFileNameFromPath(const std::string& path)
{
    std::size_t pos = path.find_last_of("/\\");
    if (pos == std::string::npos) {
        return path;
    }
    if (pos == path.size() - 1) {
        return std::string();
    }
    return path.substr(pos + 1);
}

} // unnamed namespace

// ------------------------------------------------------------------------------------------------

Application* Application::Instance()
{
    static Application instance;
    return &instance;
}

Application::Application()
{ }

Application::~Application()
{
    argc_ = nullptr;
    argv_ = nullptr;
}

std::int64_t Application::GetApplicationPid()
{
#ifdef APPLICATION_OS_WINDOWS
    return static_cast<std::int64_t>(::GetCurrentProcessId());
#elif defined(APPLICATION_OS_LINUX) || defined(APPLICATION_OS_MACOS) || defined(APPLICATION_OS_QNX) // posix
    return ::getpid();
#else
    // try, if can not call getpid(), return -1 instead. 
    return ::getpid();
#endif
}

void Application::Initialize(int& argc, char* argv[])
{
    argc_ = &argc;
    argv_ = argv;
}

int& Application::GetArgumentsCountRef()
{
    return *argc_;
}

int Application::GetArgumentsCount() const
{
    return *argc_;
}

char** Application::GetOriginalArguments()
{
    return argv_;
}

const char * const * Application::GetOriginalArguments() const
{
    return argv_;
}

std::vector<std::string> Application::GetArguments() const
{
    int argc = *argc_;
    std::vector<std::string> arguments;
    arguments.reserve(argc);
    for (int i = 0; i < argc; ++i) {
        arguments.emplace_back(argv_[i]);
    }
    return arguments;
}

std::string Application::ApplicatoinFilePath()
{
#if defined(APPLICATION_OS_WINDOWS)
    char buffer[APPLICATION_MAX_PATH] = { 0 };
    auto len = ::GetModuleFileNameA(NULL, buffer, sizeof(buffer));
    if (len > 0) {
        return std::string(buffer, len);
    }
#elif defined(APPLICATION_OS_LINUX)
    constexpr std::size_t size = APPLICATION_MAX_PATH + 1;
    char buffer[size] = { 0 };
    // #include <unistd.h>
    // char link[32] = { 0 };
    // sprintf(link, "/proc/%d/exe", getpid());
    // auto count = readlink(link, buffer, sizeof(buffer));
    auto count = ::readlink("/proc/self/exe", buffer, sizeof(buffer));
    if (count >= 0 && static_cast<std::size_t>(count) < size) {
        buffer[count] = '\0';
        return std::string(buffer, count);
    }
#elif defined(APPLICATION_OS_MACOS)
    constexpr std::size_t size = APPLICATION_MAX_PATH;
    char buffer[size] = { 0 };
    auto count = ::proc_pidpath(getpid(), buffer, sizeof(buffer));
    if (count > 0 && static_cast<std::size_t>(count) < size) {
        return std::string(buffer, count);
    }
#elif defined(APPLICATION_OS_QNX)
    std::ifstream file_stream("/proc/self/exefile");
    if (file_stream.is_open()) {
        std::string path(std::istreambuf_iterator<char>(file_stream), std::istreambuf_iterator<char>());
        // get absolute path
        char buffer[APPLICATION_MAX_PATH] = { 0 };
        char* p = ::realpath(path.c_str(), buffer);
        if (p) {
            return std::string(buffer);
        }
    }
#else
    // return default
#endif
    return std::string();
}

std::string Application::GetApplicationFilePath() const
{
    std::string path = Application::ApplicatoinFilePath();
    if (path.empty()) {
        return std::string(argv_[0] ? std::string(argv_[0]) : std::string());
    }
    return path;
}

std::string Application::ApplicationDirPath()
{
    std::string app_file_path = Application::ApplicatoinFilePath();
    if (app_file_path.empty()) {
        return std::string();
    }
    return GetDirNameFromPath(app_file_path);
}

std::string Application::GetApplicationDirPath() const
{
    std::string app_file_path = GetApplicationFilePath();
    if (app_file_path.empty()) {
        return std::string();
    }
    return GetDirNameFromPath(app_file_path);
}

std::string Application::ApplicationName()
{
    std::string app_file_path = Application::ApplicatoinFilePath();
    if (app_file_path.empty()) {
        return std::string();
    }

    return GetFileNameFromPath(app_file_path);
}

std::string Application::GetApplicationName() const
{
    std::string app_file_path(argv_[0] ? argv_[0] : "");
    if (app_file_path.empty()) {
        app_file_path = GetApplicationFilePath();
    }

    return GetFileNameFromPath(app_file_path);
}

int Application::Exec()
{
    while (exit_flag_) {
        // do something
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    return exit_code_;
}

void Application::Exit(int exit_code)
{
    exit_code_ = exit_code;
    exit_flag_ = true;
}

void Application::Quit()
{
    Exit(0);
}

void Application::ForceExit(int exit_code)
{
    std::exit(exit_code);
}

void Application::ForceQuickExit(int exit_code)
{
    std::quick_exit(exit_code);
}